import "./Calendar7.css";
import React, { useEffect, useImperativeHandle, useRef, useState } from "react";

/**
 * Date的API特性须知：
 *  new Date(年,月,日)
 *    月传参是从0开始的，返回为一月,等于12的话,返回为下一年的一月,依次类推,小于0的话返回上一年的最后一个月，依次类推
 *    日传参：1-31对应1日到31日，传0的话返回为上个月的最后一天,
 *  date实例.getMonth() 返回的月份是从0开始计起的
 *  date实例.getDay 拿到星期几。
 *  date实例.getDate()返回一个指定的日期对象为一个月中的哪一日（从 1--31）
 */

interface CalendarProps {
  value?: Date;
  defaultValue?: Date;
  onChange?: (date: Date) => void;
}

interface CalendarRef {
  getDate: () => Date;
  setDate: (date: Date) => void;
}
/**
 *
 * 这里的React.ForwardRefRenderFunction<CalendarRef,CalendarProps>是固定限制参数的
 *（props,与ref，props,与ref的顺序也是固定的，就是限制react组件函数的参数的props与ref的类型
 */

const Calendar: React.ForwardRefRenderFunction<CalendarRef, CalendarProps> = (
  props,
  ref
) => {
  const { defaultValue = new Date(), value = undefined, onChange } = props;
  //date初始化值，有传递value就使用value，否则使用defaultValue
  const [date, setDate] = useState(() => {
    if (value !== undefined) {
      return value;
    } else {
      return defaultValue;
    }
  });
  //这里使用useRef是为了让其值不会再函数每次更新调用的时候重新赋值
  const isFirstRender = useRef(true);
  useEffect(() => {
    if (value === undefined && !isFirstRender.current) {
      setDate(new Date());
    }
    isFirstRender.current = false;
  }, [value]);
  const mergedValue = value === undefined ?  date : value;
  const handlePervMouth = () => {
    const curDate = new Date(mergedValue.getFullYear(), mergedValue.getMonth() - 1, 1);
    if (value === undefined) {
      setDate(curDate);
    } else {
      onChange?.(curDate);
    }
  };
  const handleNextMouth = () => {
    const curDate = new Date(mergedValue.getFullYear(), mergedValue.getMonth() + 1, 1);
    if (value === undefined) {
      setDate(curDate);
    } else {
      onChange?.(curDate);
    }
  };

  const zhMounth = [
    "一月",
    "二月",
    "三月",
    "四月",
    "五月",
    "六月",
    "七月",
    "八月",
    "九月",
    "十月",
    "十一月",
    "十二月",
  ];

  // 获取某个月有多少天
  const daysOfMonth = (year: number, month: number) => {
    //巧妙的用下一个月（month+1）的第0日表示下个月第一天的前一天（即传参的年月当月的最后一天）,因为每个月的结束日不一样
    return new Date(year, month + 1, 0).getDate();
  };

  //获取某个月的第一天是星期几
  const firtstDayOfMounth = (year: number, month: number) => {
    //当月的第一天表示第一天，因为每个月的第一天一定是1
    return new Date(year, month, 1).getDay();
  };

  const renderDates = () => {
    const days = [];
    const daysCount = daysOfMonth(mergedValue.getFullYear(), mergedValue.getMonth());
    const firstDay = firtstDayOfMounth(mergedValue.getFullYear(), mergedValue.getMonth());

    //首先填上第一天前的空div,最多6个，也就是第一天最晚是周日撒
    for (let i = 0; i < firstDay; i++) {
      days.push(<div key={`empty-${i}`} className="empty"></div>);
    }

    //不会产生for循环闭包中的i最后都取的是最后的值，使用let的话不会产生，使用var会产生
    function clickHandler(i: number) {
      const curDate = new Date(mergedValue.getFullYear(), mergedValue.getMonth(), i);
      if (value === undefined) {
        setDate(curDate);
      } else {
        onChange?.(curDate);
      }
    }

    //接着后面的从1开始依次填上到这月的总共多少天
    for (let i = 1; i <= daysCount; i++) {
      if (i === mergedValue.getDate()) {
        days.push(
          <div key={i} onClick={() => clickHandler(i)} className="day selected">
            {i}
          </div>
        );
      } else
        days.push(
          <div onClick={() => clickHandler(i)} key={i} className="day">
            {i}
          </div>
        );
    }
    return days;
  };
  //这里return的对象的类型约束对应interface CalendarRef的约束
  useImperativeHandle(ref, () => {
    return {
      getDate: () => {
        return date;
      },
      setDate: (date) => {
        setDate(date);
      },
    };
  });

  return (
    <div className="calendar">
      <div className="header">
        <button onClick={handlePervMouth}>&lt;</button>
        <div>
          {mergedValue.getFullYear()}年{zhMounth[mergedValue.getMonth()]}
        </div>
        <button onClick={handleNextMouth}>&gt;</button>
      </div>
      <div className="content">
        <div className="day">日</div>
        <div className="day">一</div>
        <div className="day">二</div>
        <div className="day">三</div>
        <div className="day">四</div>
        <div className="day">五</div>
        <div className="day">六</div>
        {renderDates()}
      </div>
    </div>
  );
};
//想要使用函数组件中的ref的话，需要通过React.forwardRef调用后的组件，才可以使用ref属性
const CalendarComponent = React.forwardRef(Calendar);
function Test() {
  //这里泛型的对应上面的interface的CalendarRef
  const cRef = useRef<CalendarRef>(null);

  const [valueDate, setValueDate] = useState(new Date(2023, 0, 8));
  // useEffect(() => {
  //   console.log(cRef);
  //   //ref上面可以使用什么函数由组件中useImperativeHandle的第二个参数的返回值决定
  //   cRef?.current?.setDate(new Date(2023, 1, 6));
  // }, []);

  function onchange(date: Date) {
    // setValueDate(date);
    console.log(valueDate)
  }
  return (
    <div>
      <CalendarComponent
        ref={cRef}
      
        onChange={onchange}
      ></CalendarComponent>
    </div>
  );
}
export default Test;

/**
 * 受控模式和非受控模式的理解
 *     受控模式（父组件中代码可以更改子组件中的状态值）:
 *         可以通过代码改表单值 value的值就是受控模式，比如props,ref等方式暴露的进行更改(类似defaultValue初始化的形式不算入控制模式)
 *     非受控模式（父组件中代码不能更改值子组件中的状态值）:
 *         如果不能通过代码改表单值 value，那就是非受控(可以defaultValue,并不会因为这种设计而是为受控模式)
 *        
 *   非受控：
 *    子组件中
 *         const {
                  defaultValue = new Date(),
                   onChange
                   } = props;
                    //初始化value时的状态的时候复制
            const [value, setValue] = useState(defaultValue);

    受控：
           const {
                 value,
                       onChange
                  } = props;
              
          value就是里面的状态，在父组件中定义的state，子组件中引用
 *           
 *  
 */

/**
 * PropsValue+defaultValue+onChange+mergedValue实现受控与非受控并存的设计模式
     设计核心思想解读：
        1.初始化有PropsValue：
            onChange调用父组件的回调函数抛出改变后的值是多少即可，是否变化取决于父组件是否更改PropsValue的值
            内部的state除了第一次赋值，其他时候没变过
        2.初始化没PropsValue
            onChange中setSate改变state的状态

        3.mergedValue组件要使用或者展现的最终数据
          mergedValue=propsValue === undefined ? state : propsValue;  
          propsValue没传值的话就走内部的state，即非受控模式
          propsValue传值的话就拿propsValu，最终的数据由父组件的传递而决定，走受控模式
   

          上面代码验证受控模式和非受控模式
              1.受控模式
                  1.给与value值（defaultValue随便）
                  2.onchange查看返回值，不更新value和更新value视图有啥区别
                  3.查看子组件内部state变化了没

              3.非受控模式
                  1.不给value值，给或不给defaultValue
                  2.查看子组件是否正常
                  3.查看子组件内部state状态是否变化

 */

/**
 * 如何拿到dom的ref
 *     const inputRef = useRef<HTMLInputElement>(null);
 *      return <input defaultValue={'guang'} ref={inputRef}/>
 */
